<template>
    <div class="search-tree">
        <div class="search">
            <a-input
                placeholder="请输入"
                allow-clear
                v-model="searchKey"
                @change="handleSearch"
            />
            <br />
        </div>
        <!-- 虚拟滚动容器 -->
        <div class="virtual-tree" ref="container" @scroll="handleScroll">
            <!-- 虚拟滚动占位元素，高度等于所有节点总高度 -->
            <div class="virtual-scroll" :style="{ height: totalHeight + 'px' }">
                <!-- 实际渲染的节点列表，通过transform控制显示位置 -->
                <div
                    class="node-list"
                    :style="{ transform: `translateY(${renderOffset}px)` }"
                >
                    <!-- 只渲染可见区域的节点 -->
                    <div
                        v-for="node in visibleNodes"
                        :key="node.id"
                        class="tree-node"
                        :style="getNodeStyle(node)"
                    >
                        <!-- 展开/折叠图标（仅对有子节点的节点显示） -->
                        <span
                            v-if="hasChildren(node)"
                            class="expand-icon"
                            :class="{ 'is-expanded': node.expanded }"
                            @click="onExpand(node)"
                        >
                            <a-icon type="right" class="icon-arrow" />
                        </span>
                        <span v-else class="icon-placeholder"></span>

                        <!-- 复选框（根据条件决定是否显示） -->
                        <label class="tree-checkbox" v-if="node.showCheck">
                            <input
                                type="checkbox"
                                :checked="isChecked(node)"
                                @change="onCheck(node, $event)"
                            />
                            <span class="checkbox-core"></span>
                        </label>
                        <span v-else class="checkbox-placeholder"></span>

                        <!-- 节点图标和名称 -->
                        <a-icon type="user" v-if="node.nodeType === 13" />
                        <a-icon type="team" v-if="node.nodeType === 2" />
                        <span class="node-label">{{ node.name }}</span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    name: "VirtualTree",
    props: {
        // 原始树形数据
        dataSource: {
            type: Array,
            required: true,
            default: () => [],
        },
        // 每个节点的高度（用于虚拟滚动计算）
        nodeHeight: {
            type: Number,
            default: 32,
        },
        // 每级缩进量
        indent: {
            type: Number,
            default: 20,
        },
        // 缓冲区域大小（预渲染的额外节点数）
        buffer: {
            type: Number,
            default: 25,
        },
        // 默认选中的节点ID数组
        defaultNodes: {
            type: Array,
            default: () => [],
        },
    },
    data() {
        return {
            filteredData: [], // 搜索过滤后的数据
            searchKey: "", // 搜索关键词
            treeData: [], // 当前使用的树形数据（原始或过滤后）
            scrollTop: 0, // 滚动位置
            lastScrollTime: 0, // 上次滚动时间（用于节流）
            containerHeight: 0, // 容器高度
            expandedNodes: {}, // 记录展开状态的节点 { [id]: node }
            checkedStates: {}, // 记录选中状态的节点 { [id]: node }
            hiddenCheckboxes: {}, // 记录需要隐藏复选框的节点 { [id]: node }
        };
    },
    computed: {
        // 扁平化的节点数组（用于虚拟滚动）
        flatNodes() {
            const nodes = this.searchKey ? this.filteredData : this.treeData;
            return this.flattenTree(nodes);
        },
        // 所有节点的总高度（虚拟滚动用）
        totalHeight() {
            return this.flatNodes.length * this.nodeHeight;
        },
        // 渲染偏移量（虚拟滚动用）
        renderOffset() {
            return Math.max(
                0,
                Math.floor(this.scrollTop / this.nodeHeight) * this.nodeHeight -
                    this.buffer * this.nodeHeight
            );
        },
        // 可见节点的范围 [startIndex, endIndex]
        visibleRange() {
            const start = Math.max(
                0,
                Math.floor(this.scrollTop / this.nodeHeight) - this.buffer
            );
            const end =
                start +
                Math.ceil(this.containerHeight / this.nodeHeight) +
                this.buffer * 2;
            return [start, Math.min(end, this.flatNodes.length)];
        },
        // 实际需要渲染的节点
        visibleNodes() {
            const [start, end] = this.visibleRange;
            return this.flatNodes.slice(start, end);
        },
    },
    watch: {
        // 监听原始数据变化
        dataSource: {
            immediate: true,
            handler(newVal) {
                if (newVal?.length) {
                    this.treeData = [...newVal]; // 浅拷贝即可
                    this.initExpandedNodes(); // 初始化展开状态
                    this.$nextTick(() => {
                        this.updateContainerHeight();
                        this.setDefaultValues(); // 设置默认选中
                    });
                }
            },
        },
        // 监听默认选中节点变化
        defaultNodes: {
            immediate: true,
            handler(newVal) {
                if (newVal?.length && this.dataSource?.length) {
                    this.treeData = [...this.dataSource];
                    this.initExpandedNodes();
                    this.$nextTick(() => {
                        this.updateContainerHeight();
                        this.setDefaultValues();
                    });
                }
            },
        },
    },
    mounted() {
        this.updateContainerHeight();
        window.addEventListener("resize", this.updateContainerHeight);
    },
    beforeDestroy() {
        window.removeEventListener("resize", this.updateContainerHeight);
    },
    methods: {
        /**
         * 初始化展开根节点
         */
        initExpandedNodes() {
            if (this.treeData[0]?.id) {
                this.$set(
                    this.expandedNodes,
                    this.treeData[0].id,
                    this.treeData[0]
                );
            }
        },

        /**
         * 处理搜索输入变化
         */
        handleSearch() {
            if (!this.searchKey.trim()) {
                this.filteredData = []; // 清空搜索时重置过滤数据
            } else {
                // 执行搜索过滤
                this.filteredData = this.filterTree(
                    this.treeData,
                    this.searchKey.toLowerCase()
                );
                this.expandMatchingNodes(); // 展开匹配的节点路径
            }
            this.recalculateHiddenStates(); // 重新计算复选框隐藏状态
        },

        /**
         * 重新计算所有节点的复选框隐藏状态
         * 遍历所有节点，对被选中的节点隐藏其子节点复选框
         */
        recalculateHiddenStates() {
            this.hiddenCheckboxes = {};
            this.flatNodes.forEach((node) => {
                if (this.isChecked(node)) {
                    this.hideChildren(node);
                }
            });
        },

        /**
         * 递归过滤树节点
         * @param {Array} nodes - 要过滤的节点数组
         * @param {string} query - 搜索关键词
         * @returns {Array} 过滤后的节点数组
         */
        filterTree(nodes, query) {
            return nodes.reduce((list, node) => {
                const isMatch = node.name.toLowerCase().includes(query);
                if (isMatch) {
                    // 匹配的节点直接保留
                    list.push({ ...node, isMatch: true });
                    return list;
                }
                // 不匹配的节点检查子节点
                const children = node.children
                    ? this.filterTree(node.children, query)
                    : [];
                if (children.length) {
                    // 有匹配子节点的节点也保留
                    list.push({ ...node, children, isMatch: false });
                }
                return list;
            }, []);
        },

        /**
         * 展开包含匹配项的节点路径
         */
        expandMatchingNodes() {
            const expandParents = (node) => {
                if (node.parentId) {
                    this.$set(this.expandedNodes, node.parentId, node);
                    const parent = this.findNodeById(
                        this.treeData,
                        node.parentId
                    );
                    if (parent) expandParents(parent);
                }
            };
            // 对所有匹配节点展开其父节点
            this.flatNodes.forEach((node) => {
                if (node.isMatch) {
                    this.$set(this.expandedNodes, node.id, node);
                    expandParents(node);
                }
            });
        },

        /**
         * 根据ID查找节点
         * @param {Array} nodes - 要搜索的节点数组
         * @param {string} id - 要查找的节点ID
         * @returns {Object|null} 找到的节点或null
         */
        findNodeById(nodes, id) {
            for (const node of nodes) {
                if (node.id === id) return node;
                if (node.children) {
                    const found = this.findNodeById(node.children, id);
                    if (found) return found;
                }
            }
            return null;
        },

        /**
         * 更新容器高度（用于虚拟滚动计算）
         */
        updateContainerHeight() {
            this.containerHeight = this.$refs.container?.clientHeight || 0;
        },

        /**
         * 处理滚动事件（带节流）
         */
        handleScroll() {
            const now = Date.now();
            if (now - this.lastScrollTime < 16) return; // 60fps节流
            this.lastScrollTime = now;
            this.scrollTop = this.$refs.container?.scrollTop || 0;
        },

        /**
         * 获取节点样式
         * @param {Object} node - 节点对象
         * @returns {Object} 样式对象
         */
        getNodeStyle(node) {
            return {
                height: `${this.nodeHeight}px`,
                paddingLeft: `${node.level * this.indent + 8}px`, // 根据层级缩进
            };
        },

        /**
         * 扁平化树形数据
         * @param {Array} nodes - 要扁平化的节点数组
         * @param {number} level - 当前层级（默认为0）
         * @param {Object|null} parent - 父节点（默认为null）
         * @returns {Array} 扁平化后的节点数组
         */
        flattenTree(nodes, level = 0, parent = null) {
            return nodes.reduce((list, node) => {
                const isExpanded = !!this.expandedNodes[node.id];
                this.$set(node, "level", level);
                this.$set(node, "parentId", parent?.id || null);
                this.$set(node, "expanded", isExpanded);
                this.$set(node, "showCheck", !this.hiddenCheckboxes[node.id]);
                this.$set(node, "hasChildren", !!node.children?.length);
                list.push(node);
                // 如果节点是展开状态且有子节点，递归扁平化子节点
                if (isExpanded && node.children) {
                    list.push(
                        ...this.flattenTree(node.children, level + 1, node)
                    );
                }
                return list;
            }, []);
        },

        /**
         * 切换节点展开/折叠状态
         * @param {Object} node - 要切换的节点
         */
        onExpand(node) {
            if (!node.expanded) {
                this.$set(this.expandedNodes, node.id, node);
            } else {
                this.$delete(this.expandedNodes, node.id);
            }
        },

        /**
         * 切换节点选中状态
         * @param {Object} node - 要切换的节点
         * @param {Event} event - 复选框change事件
         */
        onCheck(node, event) {
            const checked = event.target.checked;
            if (checked) {
                this.$set(this.checkedStates, node.id, node);
            } else {
                this.$delete(this.checkedStates, node.id);
            }
            // 如果节点有子节点，根据选中状态隐藏/显示子节点复选框
            if (node.hasChildren) {
                checked ? this.hideChildren(node) : this.showChildren(node);
            }
            // 通知父组件选中状态变化
            this.$nextTick(() => {
                this.$emit("onCheck", this.getCheckedNodes());
            });
        },

        /**
         * 隐藏节点的所有子节点复选框
         * @param {Object} parentNode - 父节点
         */
        hideChildren(parentNode) {
            const traverse = (node) => {
                // 将节点ID添加到隐藏集合
                this.$set(this.hiddenCheckboxes, node.id, node);
                // 递归处理子节点
                node.children?.forEach(traverse);
            };
            parentNode.children?.forEach(traverse);
        },

        /**
         * 显示节点的所有子节点复选框
         * @param {Object} parentNode - 父节点
         */
        showChildren(parentNode) {
            const traverse = (node) => {
                this.$delete(this.hiddenCheckboxes, node.id);
                // 如果子节点有子节点且被选中，需要保持隐藏状态
                if (!(node.hasChildren && this.isChecked(node))) {
                    // 递归处理子节点
                    node.children?.forEach(traverse);
                }
            };
            parentNode.children?.forEach(traverse);
        },

        /**
         * 判断节点是否有子节点
         * @param {Object} node - 要检查的节点
         * @returns {boolean} 是否有子节点
         */
        hasChildren(node) {
            return node.children?.length > 0;
        },

        /**
         * 判断节点是否被选中
         * @param {Object} node - 要检查的节点
         * @returns {boolean} 是否被选中
         */
        isChecked(node) {
            return this.checkedStates[node.id] ?? false;
        },

        /**
         * 设置默认选中的节点
         */
        setDefaultValues() {
            if (!this.defaultNodes?.length) return;
            // 展开包含默认节点的父节点
            const expandParents = (nodes) => {
                nodes.forEach((node) => {
                    if (node.children) {
                        const hasDefaultChild = node.children.some((child) =>
                            this.defaultNodes.includes(child.id)
                        );
                        if (hasDefaultChild) {
                            this.$set(this.expandedNodes, node.id, node);
                            expandParents(node.children);
                        }
                    }
                });
            };
            expandParents(this.treeData);
            // 设置选中状态
            this.$nextTick(() => {
                this.defaultNodes.forEach((id) => {
                    const node = this.findNodeById(this.treeData, id);
                    if (node) {
                        this.$set(this.checkedStates, id, node);
                        if (node.hasChildren) this.hideChildren(node);
                    }
                });
            });
        },

        /**
         * 获取所有选中节点
         * @returns {Array} 选中节点数组
         */
        getCheckedNodes() {
            const checkedList = Object.values(this.checkedStates).filter(
                (item) => item.showCheck
            );
            return checkedList;
        },
    },
};
</script>

<style scoped lang="less">
.search-tree {
    height: 100%;
    min-height: 400px;
    display: flex;
    flex-direction: column;

    .search {
        padding: 16px 16px 12px;
    }
    .virtual-tree {
        padding: 0 16px;
        height: 50%;
        flex: 1;
        overflow: auto;
        position: relative;

        .virtual-scroll {
            position: relative;
        }

        .node-list {
            position: absolute;
            width: 100%;
            will-change: transform;

            .tree-node {
                display: flex;
                align-items: center;
                position: relative;
                padding-right: 12px;
                margin-bottom: 4px;
                transition: background-color 0.2s;

                &:hover {
                    background-color: rgba(255, 44, 44, 0.08);
                }
                .expand-icon {
                    width: 24px;
                    height: 24px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    cursor: pointer;
                    margin-right: 4px;

                    .icon-arrow {
                        font-size: 12px;
                        color: #c0c4cc;
                        transition: transform 0.2s;
                    }
                }
                .expand-icon.is-expanded .icon-arrow {
                    transform: rotate(90deg);
                }

                .icon-placeholder {
                    width: 24px;
                    margin-right: 4px;
                }

                .tree-checkbox {
                    position: relative;
                    margin-right: 6px;
                    display: flex;
                    input {
                        position: absolute;
                        opacity: 0;
                        width: 0;
                        height: 0;
                    }
                    .checkbox-core {
                        display: inline-block;
                        width: 16px;
                        height: 16px;
                        border: 1px solid #dcdfe6;
                        border-radius: 2px;
                        background-color: #fff;
                        position: relative;
                        transition: all 0.2s;
                    }
                    input:checked + .checkbox-core {
                        background-color: #eb4d44;
                        border-color: #eb4d44;
                    }

                    input:checked + .checkbox-core::after {
                        content: "";
                        position: absolute;
                        left: 5px;
                        top: 1px;
                        width: 5px;
                        height: 8px;
                        border: 2px solid #fff;
                        border-left: 0;
                        border-top: 0;
                        transform: rotate(45deg);
                    }
                }

                .checkbox-placeholder {
                    width: 2px;
                }

                .node-label {
                    flex: 1;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    white-space: nowrap;
                    margin-left: 4px;
                }
            }
        }
    }
}
</style>
